// Copyright (c) 2022 NetEase, Inc. All rights reserved.
// Use of this source code is governed by a MIT license that can be
// found in the LICENSE file.

package com.faceunity.nama.checkbox;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.LinearLayout;
import androidx.annotation.IdRes;

/** Created by tujh on 2018/4/17. */
public class CheckGroup extends LinearLayout {
  private static final String LOG_TAG = CheckGroup.class.getSimpleName();

  // holds the checked id; the selection is empty by default
  private int mCheckedId = View.NO_ID;
  // tracks children radio buttons checked state
  private CompoundButton.OnCheckedChangeListener mChildOnCheckedChangeListener;
  // when true, mOnCheckedChangeListener discards events
  private boolean mProtectFromCheckedChange = false;
  private OnCheckedChangeListener mOnCheckedChangeListener;
  private PassThroughHierarchyChangeListener mPassThroughListener;
  private OnDispatchActionUpListener mOnDispatchActionUpListener;

  /** {@inheritDoc} */
  public CheckGroup(Context context) {
    super(context);
    setOrientation(VERTICAL);
    init();
  }

  /** {@inheritDoc} */
  public CheckGroup(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
  }

  private void init() {
    mChildOnCheckedChangeListener = new CheckedStateTracker();
    mPassThroughListener = new PassThroughHierarchyChangeListener();
    super.setOnHierarchyChangeListener(mPassThroughListener);
  }

  public void setOnDispatchActionUpListener(OnDispatchActionUpListener onDispatchActionUpListener) {
    mOnDispatchActionUpListener = onDispatchActionUpListener;
  }

  /** {@inheritDoc} */
  @Override
  public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) {
    // the user listener is delegated to our pass-through listener
    mPassThroughListener.mOnHierarchyChangeListener = listener;
  }

  @Override
  public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_UP) {
      if (mOnDispatchActionUpListener != null) {
        mOnDispatchActionUpListener.onDispatchActionUp((int) ev.getX());
      }
    }
    return super.dispatchTouchEvent(ev);
  }

  /** {@inheritDoc} */
  @Override
  protected void onFinishInflate() {
    super.onFinishInflate();

    // checks the appropriate radio button as requested in the XML file
    if (mCheckedId != View.NO_ID) {
      mProtectFromCheckedChange = true;
      setCheckedStateForView(mCheckedId, true);
      mProtectFromCheckedChange = false;
      setCheckedId(mCheckedId);
    }
  }

  @Override
  public void addView(View child, int index, ViewGroup.LayoutParams params) {
    if (child instanceof CheckBox) {
      final CheckBox button = (CheckBox) child;
      if (button.isChecked()) {
        mProtectFromCheckedChange = true;
        if (mCheckedId != View.NO_ID) {
          setCheckedStateForView(mCheckedId, false);
        }
        mProtectFromCheckedChange = false;
        setCheckedId(button.getId());
      }
    }

    super.addView(child, index, params);
  }

  /**
   * Sets the selection to the radio button whose identifier is passed in parameter. Using
   * View.NO_ID as the selection identifier clears the selection; such an operation is equivalent to
   * invoking {@link #clearCheck()}.
   *
   * @param id the unique id of the radio button to select in this group
   * @see #getCheckedCheckBoxId()
   * @see #clearCheck()
   */
  public void check(@IdRes int id) {
    // don't even bother
    if (id != View.NO_ID && (id == mCheckedId)) {
      return;
    }

    if (mCheckedId != View.NO_ID) {
      setCheckedStateForView(mCheckedId, false);
    }

    if (id != View.NO_ID) {
      setCheckedStateForView(id, true);
    }

    setCheckedId(id);
  }

  private void setCheckedId(@IdRes int id) {
    mCheckedId = id;
    if (mOnCheckedChangeListener != null) {
      mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);
    }
  }

  private void setCheckedStateForView(int viewId, boolean checked) {
    View checkedView = findViewById(viewId);
    if (checkedView != null && checkedView instanceof CheckBox) {
      ((CheckBox) checkedView).setChecked(checked);
    }
  }

  /**
   * Returns the identifier of the selected radio button in this group. Upon empty selection, the
   * returned value is View.NO_ID.
   *
   * @return the unique id of the selected radio button in this group
   * @attr ref android.R.styleable#CheckGroup_checkedButton
   * @see #check(int)
   * @see #clearCheck()
   */
  @IdRes
  public int getCheckedCheckBoxId() {
    return mCheckedId;
  }

  /**
   * Clears the selection. When the selection is cleared, no radio button in this group is selected
   * and {@link #getCheckedCheckBoxId()} returns null.
   *
   * @see #check(int)
   * @see #getCheckedCheckBoxId()
   */
  public void clearCheck() {
    check(View.NO_ID);
  }

  /**
   * Register a callback to be invoked when the checked radio button changes in this group.
   *
   * @param listener the callback to call on checked state change
   */
  public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {
    mOnCheckedChangeListener = listener;
  }

  @Override
  public CharSequence getAccessibilityClassName() {
    return CheckGroup.class.getName();
  }

  /**
   * Interface definition for a callback to be invoked when the checked radio button changed in this
   * group.
   */
  public interface OnCheckedChangeListener {
    /**
     * Called when the checked radio button has changed. When the selection is cleared, checkedId is
     * View.NO_ID.
     *
     * @param group the group in which the checked radio button has changed
     * @param checkedId the unique identifier of the newly checked radio button
     */
    public void onCheckedChanged(CheckGroup group, @IdRes int checkedId);
  }

  private class CheckedStateTracker implements CompoundButton.OnCheckedChangeListener {
    @Override
    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
      // prevents from infinite recursion
      if (mProtectFromCheckedChange) {
        return;
      }

      int id = buttonView.getId();
      mProtectFromCheckedChange = true;
      if (mCheckedId != View.NO_ID && mCheckedId != id) {
        setCheckedStateForView(mCheckedId, false);
      }
      mProtectFromCheckedChange = false;
      buttonView.setChecked(true);
      setCheckedId(id);
    }
  }

  /**
   * A pass-through listener acts upon the events and dispatches them to another listener. This
   * allows the table layout to set its own internal hierarchy change listener without preventing
   * the user to setup his.
   */
  private class PassThroughHierarchyChangeListener implements OnHierarchyChangeListener {
    private OnHierarchyChangeListener mOnHierarchyChangeListener;

    /** {@inheritDoc} */
    @Override
    public void onChildViewAdded(View parent, View child) {
      if (parent == CheckGroup.this && child instanceof CheckBox) {
        int id = child.getId();
        // generates an id if it's missing
        if (id == View.NO_ID) {
          id = View.generateViewId();
          child.setId(id);
        }
        ((CheckBox) child).setOnCheckedChangeListener(mChildOnCheckedChangeListener);
      }

      if (mOnHierarchyChangeListener != null) {
        mOnHierarchyChangeListener.onChildViewAdded(parent, child);
      }
    }

    /** {@inheritDoc} */
    @Override
    public void onChildViewRemoved(View parent, View child) {
      if (parent == CheckGroup.this && child instanceof CheckBox) {
        ((CheckBox) child).setOnCheckedChangeListener(null);
      }

      if (mOnHierarchyChangeListener != null) {
        mOnHierarchyChangeListener.onChildViewRemoved(parent, child);
      }
    }
  }

  public interface OnDispatchActionUpListener {
    /**
     * 分发 action up 事件时回调
     *
     * @param x
     */
    void onDispatchActionUp(int x);
  }
}
